import { existsSync } from 'fs';
import { join } from 'path';
import { URL } from 'url';
import { ConnectOptions } from './base/connection';
import { PoolOptions } from './base/pool';

const OPTIONS_FILE = '.lubejs';

export interface LubeConfig {
  defaultConnection: string;
  defaultPool: string;
  connections: {
    [key: string]: ConnectOptions;
  };
  pools: {
    [key: string]: PoolOptions & {
      connection: string;
    };
  };
}

export async function loadConfig(): Promise<LubeConfig> {
  let configFile = join(process.cwd(), OPTIONS_FILE);
  if (existsSync(configFile + '.js')) {
    configFile = configFile + '.js';
  } else if (existsSync(configFile + '.ts')) {
    configFile = configFile + '.ts';
  } else {
    throw new Error(
      `Configure file '.lubejs(.ts|.js)' not found in dir ${process.cwd()}, use 'lube init' to create it.`
    );
  }
  let config: LubeConfig;
  try {
    const imported = await import(configFile);
    config = imported?.default ?? imported;
  } catch (error) {
    throw new Error(
      `Occur error at load configure ${configFile}, error info: ${error}`
    );
  }
  return config;
}

export async function getDefaultConnectionOptions(): Promise<ConnectOptions> {
  const config = await loadConfig();
  if (!config.defaultConnection) {
    throw new Error(`Invalid configure, 'defaultConnection' setion not found.`);
  }
  const opts = config.connections[config.defaultConnection];
  if (!opts) {
    throw new Error(
      `Invalid configure, default connection option ${config.defaultPool} not found.`
    );
  }
  return opts;
}

export async function getConnectionOptions(
  key: string
): Promise<ConnectOptions> {
  const config = await loadConfig();
  const opts = config.connections[key];
  if (!opts) {
    throw new Error(`Connection option ${key} not found in configure.`);
  }
  return opts;
}

export async function getDefaultPoolOptions(): Promise<
  PoolOptions & { connection: ConnectOptions }
> {
  const config = await loadConfig();
  const opts = config.pools[config.defaultPool];
  if (!opts) {
    throw new Error(
      `Invalid configure, default pool option ${config.defaultPool} not found.`
    );
  }
  const connOpts = config.connections[opts.connection];
  if (!connOpts) {
    throw new Error(
      `Invalid configure, pool options ${config.defaultPool} connection ${opts.connection} not found.`
    );
  }
  return Object.assign({}, opts, {
    connection: config.connections[opts.connection],
  });
}

export async function getPoolOptions(
  key: string
): Promise<PoolOptions & { connection: ConnectOptions }> {
  const config = await loadConfig();
  const opts = config.pools[key];
  if (!opts) {
    throw new Error(`Pool option ${key} not found in configure.`);
  }
  const connOpts = config.connections[opts.connection];
  if (!connOpts) {
    throw new Error(
      `Invalid configure, pool options ${config.defaultPool} connection ${opts.connection} not found.`
    );
  }
  return Object.assign({}, opts, {
    connection: config.connections[opts.connection],
  });
}

export function parseConnectionUrl(url: string): ConnectOptions {
  const uri = new URL(url);
  const params = uri.searchParams;
  const urlOptions: Record<string, string> = {};
  for (const [key, value] of params.entries()) {
    if (value !== undefined) {
      urlOptions[key] = value;
    }
  }
  const dialect = uri.protocol.substr(0, uri.protocol.length - 1).toLowerCase();
  const paths = uri.pathname.split('/');
  let database: string | undefined;
  if (!paths[0]) {
    database = paths[1];
  } else {
    database = paths[0];
  }
  if (!database) {
    throw new Error('Database must specify.');
  }
  const options = {
    dialect,
    host: uri.host,
    port: uri.port ? parseInt(uri.port) : undefined,
    user: uri.username,
    password: uri.password,
    database,
    ...urlOptions,
  };
  return options;
}

// const DEFAULT_CONNECT_OPTIONS: Partial<ConnectOptions> = {
//   connectionTimeout: 30,
//   requestTimeout: 10 * 60,
//   // minConnections: 0,
//   // maxConnections: 5,
// };

/**
 * 准备连接选项
 */
export async function parseConnectOption(
  optOrUrlOrCfg?: ConnectOptions | string
): Promise<ConnectOptions> {
  let options: ConnectOptions | undefined;
  if (!optOrUrlOrCfg) {
    options = await getDefaultConnectionOptions();
    if (!options) {
      throw new Error(`Default connection options not found on config file.`);
    }
  } else if (typeof optOrUrlOrCfg === 'string') {
    try {
      new URL(optOrUrlOrCfg);
      options = parseConnectionUrl(optOrUrlOrCfg);
    } catch {
      const config = await loadConfig();
      options = config.connections[optOrUrlOrCfg];
      if (!options) {
        throw new Error(
          `Connection options ${optOrUrlOrCfg} not found on config file.`
        );
      }
    }
  } else {
    options = optOrUrlOrCfg;
  }
  return options;
}
